/*
 * Time.cpp
 *
 *  Created on: Jun 16, 2013
 *      Author: Lucifer
 */

#include "../pch.h"
#include "Time.h"
#include <time.h>

#ifndef	_WIN32
#include <sys/time.h>

#ifdef	__APPLE_CC__
//	__APPLE_CC__
// MacOS X doesn't have _timezone (i.e., HAVE_TIME_T_TIMEZONE and HAVE_TIME_T__TIMEZONE are undefined).
long timezone;
#define	_timezone	timezone

#endif	//	__APPLE_CC__
#endif	//	_WIN32

namespace BFX
{

////////////////////////////////////////////////////////////////////////////////
// Time

int Time::DaysToMonth365[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
int Time::DaysToMonth366[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };

/**
 * Converts the value of the current BFX::Time object to local time.
 * @return The local time.
 */
Time Time::ToLocalTime() const
{
	long lTimezone = Time::GetSysTimeZone();
	if (m_lTimezone == lTimezone)
		return (*this);

	INT64 lNewTicks;
	if (m_lTicks <= MinTicks)
		lNewTicks = MinTicks;
	else if (m_lTicks >= MaxTicks)
		lNewTicks = MaxTicks;
	else
	{
		long diff = lTimezone - m_lTimezone;
		lNewTicks = m_lTicks - (diff * Time::TicksPerSecond);
	}
	return Time(lNewTicks, lTimezone);
}

/**
 * Converts the value of the current BFX::Time object to UTC time.
 * @return The UTC time.
 */
Time Time::ToUtcTime() const
{
	if (m_lTimezone == 0)
		return (*this);

	INT64 lUtcTicks;
	if (m_lTicks <= MinTicks)
		lUtcTicks = MinTicks;
	else if  (m_lTicks >= MaxTicks)
		lUtcTicks = MaxTicks;
	else
		lUtcTicks = m_lTicks + (m_lTimezone * Time::TicksPerSecond);
	return Time(lUtcTicks, 0);
}

/**
 * Retrieves the current system universal time (UTC).
 * @return the current system UTC format date and time, in microseconds.
 */
INT64 Time::GetSysUtcTicks()
{
#ifdef	_WIN32
	FILETIME ft;
	UINT64 lResult;
	GetSystemTimeAsFileTime(&ft);
	lResult = ((((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime);
	lResult /= 10; // convert into microseconds.
#else
	/*
	 * Magic number explanation:
	 * Both epochs are Gregorian. 1970 - 1601 = 369. Assuming a leap year every four years, 369 / 4 = 92.
	 * However, 1700, 1800, and 1900 were NOT leap years, so 89 leap years, 280 non-leap years.
	 * 89 * 366 + 280 * 365 = 134744 days between epochs. Of course  60 * 60 * 24 = 86400 seconds per day,
	 * so 134744 * 86400 = 11644473600 = SECS_BETWEEN_EPOCHS.
	 * 
	 * This result is also confirmed in the MSDN documentation on how to convert a time_t value to a win32 FILETIME.
	 */
	const INT64 SECS_BETWEEN_EPOCHS	= 11644473600LL;
	struct timeval tv;
	int nRetVal = gettimeofday(&tv, NULL);
	BFX_ASSERT(nRetVal == 0);

	INT64 lResult = ((INT64)tv.tv_sec + SECS_BETWEEN_EPOCHS) * 1000000 + tv.tv_usec;
#endif
    // Number of days from 1/1/0001 to 12/31/1600
    const int DaysTo1601 = DaysPer400Years * 4;
    const INT64 TicksOffset = DaysTo1601 * TicksPerDay;

	return lResult + (TicksOffset);
}

/**
 * Retrieves current system universal (UTC) time-zone.
 * @return Difference in seconds between local time and UTC. The default value is 28800 (28800 seconds equals 8 hours)
 *
 */
long Time::GetSysTimeZone()
{
	tzset();	// NOTE: we need just run this function once.
	return _timezone;
}

// Return the tick count corresponding to the given year, month, day, hour, minute, second. Will check the if the
// parameters are valid.
INT64 Time::TimeToTicks(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond)
{
	BFX_REQUIRE(nYear >= 1 && nYear <= 9999 && nMonth >= 1 && nMonth <= 12);
	BFX_REQUIRE(nHour >= 0 && nHour < 24 && nMinute >= 0 && nMinute < 60 && nSecond >=0 && nSecond < 60);

	int* days = IsLeapYear(nYear) ? DaysToMonth366: DaysToMonth365;
	BFX_ENSURE(nDay >= 1 && nDay <= days[nMonth] - days[nMonth - 1]);

	int y = nYear - 1;
	int n = y * 365 + y / 4 - y / 100 + y / 400 + days[nMonth - 1] + nDay - 1;
	return (n * TicksPerDay) + TimeSpan::TimeToTicks(nHour, nMinute, nSecond);
}

// Checks whether a given year is a leap year. This method
// returns true if year is a leap year, or false if not.
bool Time::IsLeapYear(int nYear)
{
	BFX_REQUIRE(nYear >= 1 && nYear <= 9999);

	return nYear % 4 == 0 && (nYear % 100 != 0 || nYear % 400 == 0);
}

// Returns a given date part of this DateTime. This method is used
// to compute the year, day-of-year, month, or day part.
int Time::GetDatePart(int part) const
{
	INT64 ticks = m_lTicks;
    // n = number of days since 1/1/0001
    int n = (int)(ticks / TicksPerDay);
    // y400 = number of whole 400-year periods since 1/1/0001
    int y400 = n / DaysPer400Years;
    // n = day number within 400-year period
    n -= y400 * DaysPer400Years;
    // y100 = number of whole 100-year periods within 400-year period
    int y100 = n / DaysPer100Years;
    // Last 100-year period has an extra day, so decrement result if 4
    if (y100 == 4) y100 = 3;
    // n = day number within 100-year period
    n -= y100 * DaysPer100Years;
    // y4 = number of whole 4-year periods within 100-year period
    int y4 = n / DaysPer4Years;
    // n = day number within 4-year period
    n -= y4 * DaysPer4Years;
    // y1 = number of whole years within 4-year period
    int y1 = n / DaysPerYear;
    // Last year has an extra day, so decrement result if 4
    if (y1 == 4) y1 = 3;
    // If year was requested, compute and return it
    if (part == DatePartYear)
    {
        return (y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1);
    }
    // n = day number within year
    n -= y1 * DaysPerYear;
    // If day-of-year was requested, return it
    if (part == DatePartDayOfYear)
    	return n + 1;
    // Leap year calculation looks different from IsLeapYear since y1, y4,
    // and y100 are relative to year 1, not year 0
    bool bLeapYear = y1 == 3 && (y4 != 24 || y100 == 3);
    int* days = bLeapYear? DaysToMonth366 : DaysToMonth365;
    // All months have less than 32 days, so n >> 5 is a good conservative
    // estimate for the month
    int m = (n >> 5) + 1;
    // m = 1-based month number
    while (n >= days[m])
    	m++;

    // If month was requested, return it
    if (part == DatePartMonth)
    	return m;
    // Return 1-based day-of-month
    return n - days[m - 1] + 1;
}

} /* namespace BFX */
